getwd()[1] "/Users/zhaoxiang/Documents/tmp/ecocommons-marxan/ecocommons-marxan-integration-poc"
Author: Zhao Xiang, EcoCommons
Date: 2024-09-24
Using the Species distribution modeling techniques provided by the EcoCommons Platform (www.ecocommons.org.au), we produced probability distribution maps for the three Queensland endangered species: koala, brush tailed rock-wallaby, and beach stone curlew.
Then we adjusted the probability distribution maps of these three species with the planning units shapefile prepared by the Marxan MaPP, and ran four planning scenarios with a target of expanding the coverage of protected areas in QLD to 30%.
Make sure you are in the directory you want
getwd()[1] "/Users/zhaoxiang/Documents/tmp/ecocommons-marxan/ecocommons-marxan-integration-poc"
Create a virtual environment to install and load all essential packages
# install.packages("renv") # install this package if you have not.
renv::init()Install and load essential packages
install.packages("sf")The following package(s) will be installed:
- sf [1.0-17]
These packages will be installed into "~/Documents/tmp/ecocommons-marxan/ecocommons-marxan-integration-poc/renv/library/macos/R-4.4/x86_64-apple-darwin20".
# Installing packages --------------------------------------------------------
- Installing sf ... OK [linked from cache]
Successfully installed 1 package in 10 milliseconds.
# First specify the packages of interest
packages = c("sf", "terra", "ggplot2", "ggspatial", "raster", "dplyr", "shiny", "httpuv", "rmarkdown", "knitr", "jsonlite", "reticulate", "htmltools", "pryr")
# Now load or install&load all. This process will take a long time since we are using a virtual environment and install a lot of packages.
package.check <- lapply(
packages,
FUN = function(x) {
if (!require(x, character.only = TRUE)) {
install.packages(x, dependencies = TRUE)
library(x, character.only = TRUE)
}
}
)
# You might need to link your renv to proj
Sys.setenv(PROJ_LIB = "/usr/local/Cellar/proj/9.4.1/share/proj") # Update path if different
renv::snapshot()- The lockfile is already up to date.
1. We get the QLD planning units from Marxan MaPP
# Helper function to convert bytes to GB
mem_in_gb <- function(memory_in_bytes) {
return(memory_in_bytes / (1024^3)) # Convert bytes to GB
}
# Record memory usage before reading the shapefile
cat("Memory usage before reading shapefile: ", mem_in_gb(pryr::mem_used()), " GB\n")
QLD_Unit <- "qld_3species_Marxan/QLD_plannningunits/cost-surface-template.shp" #This cost-surface-template was prepared by the Marxan Mapp with a resolution of 189 Km2, which is the highest resolution Marxan Mapp can give at this scale.
QLD_Unit <- st_read(QLD_Unit)
st_crs(QLD_Unit) <- 4326 # Example with WGS84 CRS
QLD_Unit <- st_simplify(QLD_Unit , dTolerance = 0.01)
# Record memory usage after reading the shapefile
cat("Memory usage after reading shapefile: ", mem_in_gb(pryr::mem_used()), " GB\n")
# Calculate the resolution since Marxan MaPP for visulization purpose
areas <- st_area(QLD_Unit)
areas_numeric <- as.numeric(areas)
average_area <- mean(areas_numeric)
# Record memory usage after calculating the resolution
cat("Memory usage after calculating areas: ", mem_in_gb(pryr::mem_used()), " GB\n")
# Convert to numeric
average_area_km2 <- average_area / 1e6
# Get the number of rows
n_rows <- nrow(QLD_Unit)
# Plot the shapefile with no fill color and number of rows in the title
ggplot(data = QLD_Unit) +
geom_sf(fill = NA, color = "gray") +
theme_minimal() +
ggtitle(paste("QLD Planning Units:", n_rows, "\n",
"Resolution of planning in square kilometers:", round(average_area_km2)))+
theme(plot.title = element_text(hjust = 0.5)) # Center the title2. I made a cost layer using the reciprocal of the distance to state-owned road as a surrogate of the cost.
The assumption is: the closer to the state owned road, the more expensive to purchase the unit.
QLD_cost_road <- st_read("qld_3species_Marxan/QLD_Cost/QLD_cost_road.shp")
# Plot the shapefile with continuous cost_road values
ggplot(QLD_cost_road) +
geom_sf(aes(fill = cost_road)) +
scale_fill_continuous(name = "Cost",
low = "lightblue", high = "red",
labels = c("0 (Low cost)", "1 (High cost)"),
breaks = c(0.01, 1)) +
theme_minimal() +
labs(title = "Cost: using the distance to road of each Unit as a proxy")+
theme(plot.title = element_text(hjust = 0.5)) # Center the title3. Biodiversity features. I used EcoCommons to produce three species’ SDM to start with.
Species 1: koala
Species 2: brush tailed rock-wallaby
Species 3: beach stone curlew
# Define the folder path where the rasters are stored
folder_path <- "qld_3species_Marxan/QLD_feature/"
# Get a list of all .tif files in the folder
raster_files <- list.files(path = folder_path, pattern = "\\.tif$", full.names = TRUE)
# Extract the species names from the file names (removing the folder path and .tif extension)
species_names <- tools::file_path_sans_ext(basename(raster_files))
# Read all raster files in one go using lapply
raster_list <- lapply(raster_files, rast) # Use rast() from terra for reading rasters
# Using QLD_Unit as the spatial vector for masking
# Transform the raster CRS to match the vector CRS and apply masking in one step
raster_list <- lapply(raster_list, function(r) {
r_transformed <- project(r, crs(vect(QLD_Unit)))
mask(r_transformed, vect(QLD_Unit))
})
# Function to convert rasters to data frames and combine them
prepare_raster_data <- function(raster_list, species_names) {
# Initialize an empty data frame
combined_df <- data.frame()
# Loop through each raster and combine them into one data frame
for (i in seq_along(raster_list)) {
# Convert raster to a data frame
raster_df <- as.data.frame(raster_list[[i]], xy = TRUE)
# Rename the third column to 'value' or any appropriate name for the raster values
names(raster_df)[3] <- "value"
# Add a column to identify the species name
raster_df$species <- species_names[i]
# Combine the raster data with the overall data frame
combined_df <- bind_rows(combined_df, raster_df)
}
return(combined_df)
}
# Prepare the combined data frame
combined_raster_df <- prepare_raster_data(raster_list, species_names)
# Create the ggplot with facet_wrap to display each raster in a separate facet
ggplot(combined_raster_df, aes(x = x, y = y, fill = value)) + # Use the correct column name for fill
geom_raster()+
facet_wrap(~ species, ncol = 3) + # Adjust ncol to control the number of columns
scale_fill_viridis_c() + # You can adjust the color scale as needed
labs(title = "Species SDM") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5))4. We need to turn these SDMs to binary results (shapefies).
5. Plot species SDM binary shapefile outputs for double check
output_dir <- "qld_3species_Marxan/QLD_feature/Marxan_feature_input/"
# List all the shapefiles in the directory (assuming each species has its own shapefile)
species_files <- list.files(output_dir, pattern = "\\.shp$", full.names = TRUE)
species_files
# Extract species names from the filenames (you can adjust this depending on your naming conventions)
species_names <- tools::file_path_sans_ext(basename(species_files))
# Load all species shapefiles and add a species identifier
species_sf_list <- lapply(seq_along(species_files), function(i) {
sf <- st_read(species_files[i])
sf$species <- species_names[i] # Add species name column
return(sf)
})
# Combine all species into one dataset
combined_species_sf <- do.call(rbind, species_sf_list)
# Plot the unit (base map) first and overlay the species habitats without borders
combined_plot_with_unit <- ggplot() +
geom_sf(data = QLD_Unit, fill = NA, color = "grey", linewidth = 0.01) + # Base map (QLD Unit)
geom_sf(data = combined_species_sf, aes(fill = species), color = NA) + # No borders for species
scale_fill_manual(values = RColorBrewer::brewer.pal(n = length(species_names), name = "Set1")) + # Automatically assign colors
theme_minimal() +
labs(title = "Species Habitats within QLD Unit",
subtitle = paste(species_names, collapse = ", ")) + # List all species in subtitle
theme(legend.title = element_blank())
# Display the plot
print(combined_plot_with_unit)6. We can also make a species presence and absence csv table.
# Function to extract presence (1) and absence (0) from raster based on a threshold (e.g., 0.5)
extract_presence_absence <- function(raster_data, unit) {
extracted_values <- extract(raster_data, vect(unit), fun = mean, na.rm = TRUE)
presence_absence <- ifelse(extracted_values[, 2] >= 0.5, 1, 0)
return(presence_absence)
}
# Create an empty presence-absence data frame
presence_absence_df <- data.frame(puid = QLD_Unit$puid) # Assuming 'puid' is the unique identifier
# Loop through each species raster in the raster list and extract presence-absence data
for (i in seq_along(raster_list)) {
# Generate a dynamic presence column name for the current species
presence_col_name <- paste0(species_names[i], "_presence")
# Extract presence/absence data and add it to the presence-absence dataframe
presence_absence_df[[species_names[i]]] <- extract_presence_absence(raster_list[[i]], QLD_Unit)
}
# Write the final presence-absence data frame to a CSV file
output_csv <- file.path(output_dir, "presence_absence_species.csv")
write.csv(presence_absence_df, output_csv, row.names = FALSE)
# Check the CSV output
print(head(presence_absence_df)) puid beach_stone_curlew_GLM brushtailed_rockwallaby_GLM Koala_GLM
1 1 0 0 0
2 2 0 0 0
3 3 0 0 0
4 4 0 0 0
5 5 0 0 0
6 6 0 0 0